package com.db.williamchart.renderer;

import com.db.williamchart.ChartContract;
import com.db.williamchart.Painter;
import com.db.williamchart.animation.ChartAnimation;
import com.db.williamchart.data.*;
import com.db.williamchart.data.configuration.BarChartConfiguration;
import com.db.williamchart.data.configuration.ChartConfiguration;
import com.db.williamchart.extensions.ListEx;
import com.db.williamchart.renderer.executor.*;
import ohos.utils.Pair;

import java.util.ArrayList;
import java.util.List;

public class BarChartRenderer implements ChartContract.Renderer {
    private ChartContract.BarView view;
    private Painter painter;
    private ChartAnimation<DataPoint> animation;

    private List<DataPoint> data = new ArrayList<>();
    private Frame outerFrame;
    private Frame innerFrame;
    private BarChartConfiguration chartConfiguration;
    private List<Label> xLabels;
    private List<Label> yLabels;

    public BarChartRenderer(ChartContract.BarView view, Painter painter, ChartAnimation<DataPoint> animation) {
        this.view = view;
        this.painter = painter;
        this.animation = animation;
    }


    @Override
    public Boolean preDraw(ChartConfiguration configuration) {
        if (data.isEmpty()) {
            return true;
        }
        chartConfiguration = (BarChartConfiguration) configuration;
        if (chartConfiguration.getScale().notInitialized()) {
            chartConfiguration.setScale(new Scale(0f, ListEx.limits(data).s));
        }

        xLabels = ListEx.toLabels(data);

        float scaleStep = chartConfiguration.getScale().getSize() / RendererConstants.DEFAULT_SCALE_NUMBER_OF_STEPS;

        int size = RendererConstants.DEFAULT_SCALE_NUMBER_OF_STEPS + 1;
        yLabels = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            float scaleValue = chartConfiguration.getScale().getMin() + scaleStep * i;
            yLabels.add(i, new Label(Float.toString(scaleValue), 0f, 0f));
        }
        float longestChartLabelWidth = 0f;
        for (Label label : yLabels) {
            longestChartLabelWidth = Math.max(longestChartLabelWidth, painter.measureLabelWidth(label.getLabel(), chartConfiguration.getLabelSize()));
        }

        MeasureBarChartPaddings measureBarChartPaddings = new MeasureBarChartPaddings();
        Paddings paddings = measureBarChartPaddings.invoke(chartConfiguration.getAxis(),
                painter.measureLabelHeight(chartConfiguration.getLabelSize()),
                longestChartLabelWidth,
                RendererConstants.LABELS_PADDING_TO_INNER_CHART);

        outerFrame = chartConfiguration.toOuterFrame();
        innerFrame = outerFrame.withPaddings(paddings);

        placeLabelsX(innerFrame);
        placeLabelsY(innerFrame);
        placeDataPoints(innerFrame);

        animation.animateFrom(innerFrame.getBottom(), data, new ChartAnimation.ChartCallback() {
            @Override
            public void invoke(Float value) {
                view.postInvalidate();
            }
        });
        return true;
    }

    @Override
    public void draw() {
        if (data.isEmpty()) {
            return;
        }
        if (chartConfiguration.getAxis().shouldDisplayAxisX()) {
            view.drawLabels(xLabels);
        }
        if (chartConfiguration.getAxis().shouldDisplayAxisY()) {
            view.drawLabels(yLabels);
        }

        List<Float> xLabelsPositions = new ArrayList<>();
        List<Float> yLabelsPositions = new ArrayList<>();

        for (Label label : xLabels){
            xLabelsPositions.add(label.getScreenPositionX());
        }
        for (Label label : yLabels){
            yLabelsPositions.add(label.getScreenPositionY());
        }
        view.drawGrid(innerFrame,xLabelsPositions,yLabelsPositions);

        if (chartConfiguration.getBarsBackgroundColor() != -1){
            GetVerticalBarBackgroundFrames barBackgroundFrames = new GetVerticalBarBackgroundFrames();
            view.drawBarsBackground(barBackgroundFrames.invoke(innerFrame, chartConfiguration.getBarsSpacing(), data));
        }
        GetVerticalBarFrames barFrames = new GetVerticalBarFrames();
        view.drawBars(barFrames.invoke(innerFrame, chartConfiguration.getBarsSpacing(), data));
        if (RendererConstants.IN_DEBUG){
            List<Frame> frames = new ArrayList<>();
            frames.add(outerFrame);
            frames.add(innerFrame);
            DebugWithLabelsFrame debugWithLabelsFrame = new DebugWithLabelsFrame();
            frames.addAll(debugWithLabelsFrame.invoke(painter,chartConfiguration.getAxis(),xLabels,yLabels,chartConfiguration.getLabelSize()));

            DefineVerticalBarsClickableFrames defineVerticalBarsClickableFrames = new DefineVerticalBarsClickableFrames();
            List<Pair<Float,Float>> list = new ArrayList<>();
            for (DataPoint dataPoint : data){
                list.add(new Pair<>(dataPoint.getScreenPositionX(),dataPoint.getScreenPositionY()));
            }
            frames.addAll(defineVerticalBarsClickableFrames.invoke(innerFrame, list));
            view.drawDebugFrame(frames);
        }
    }

    @Override
    public void render(List<Pair<String, Float>> entries) {
        data = new ArrayList<>();
        for (Pair<String,Float> pair : entries){
            data.add(new DataPoint(pair.f,pair.s,0f,0f));
        }
        view.postInvalidate();
    }

    @Override
    public void anim(List<Pair<String, Float>> entries, ChartAnimation<DataPoint> animation) {
        data = new ArrayList<>();
        for (Pair<String,Float> pair : entries){
            data.add(new DataPoint(pair.f,pair.s,0f,0f));
        }
        this.animation = animation;
        view.postInvalidate();
    }

    @Override
    public Triple<Integer, Float, Float> processClick(Float x, Float y) {
        if (x == null || y == null || data.isEmpty()){
            return new Triple<>(-1,-1f,-1f);
        }
        DefineVerticalBarsClickableFrames defineVerticalBarsClickableFrames = new DefineVerticalBarsClickableFrames();
        List<Pair<Float,Float>> list = new ArrayList<>();
        for (DataPoint dataPoint : data){
            list.add(new Pair<>(dataPoint.getScreenPositionX(),dataPoint.getScreenPositionY()));
        }
        List<Frame> invoke = defineVerticalBarsClickableFrames.invoke(innerFrame, list);
        int index = -1;
        int i = 0;
        for (Frame frame : invoke){
            if (frame.contains(x, y)){
                index = i;
            }
            i++;
        }

        return index != -1 ? new Triple<>(index,data.get(index).getScreenPositionX(),data.get(index).getScreenPositionY())
                : new Triple<>(-1,-1f,-1f);
    }

    @Override
    public Triple<Integer, Float, Float> processTouch(Float x, Float y) {
        return processClick(x, y);
    }

    public List<DataPoint> getData() {
        return data;
    }

    public void setData(List<DataPoint> data) {
        this.data = data;
    }

    public Frame getOuterFrame() {
        return outerFrame;
    }

    public void setOuterFrame(Frame outerFrame) {
        this.outerFrame = outerFrame;
    }

    public Frame getInnerFrame() {
        return innerFrame;
    }

    public void setInnerFrame(Frame innerFrame) {
        this.innerFrame = innerFrame;
    }

    public BarChartConfiguration getChartConfiguration() {
        return chartConfiguration;
    }

    public void setChartConfiguration(BarChartConfiguration chartConfiguration) {
        this.chartConfiguration = chartConfiguration;
    }

    public List<Label> getxLabels() {
        return xLabels;
    }

    public List<Label> getyLabels() {
        return yLabels;
    }

    private void placeLabelsX(Frame innerFrame) {

        float halfBarWidth = (innerFrame.getRight() - innerFrame.getLeft()) / xLabels.size() / 2;
        float labelsLeftPosition = innerFrame.getLeft() + halfBarWidth;
        float labelsRightPosition = innerFrame.getRight() - halfBarWidth;
        float widthBetweenLabels = (labelsRightPosition - labelsLeftPosition) / (xLabels.size() - 1);
        float yLabelsVerticalPosition =
                innerFrame.getBottom() -
                        painter.measureLabelAscent(chartConfiguration.getLabelSize()) +
                        RendererConstants.LABELS_PADDING_TO_INNER_CHART;

        List<Label> list = new ArrayList<>();
        int i = 0;
        for (Label label : xLabels){
            label.setScreenPositionX(labelsLeftPosition + (widthBetweenLabels * i));
            label.setScreenPositionY(yLabelsVerticalPosition);
            list.add(label);
            i++;
        }
        xLabels.clear();
        xLabels.addAll(list);

    }

    private void placeLabelsY(Frame innerFrame) {

        float heightBetweenLabels =
                (innerFrame.getBottom() - innerFrame.getTop()) / RendererConstants.DEFAULT_SCALE_NUMBER_OF_STEPS;
        float labelsBottomPosition =
                innerFrame.getBottom() + painter.measureLabelHeight(chartConfiguration.getLabelSize()) / 2;
        List<Label> list = new ArrayList<>();
        int i = 0;
        for (Label label : yLabels){
            label.setScreenPositionX(innerFrame.getLeft() -
                    RendererConstants.LABELS_PADDING_TO_INNER_CHART -
                    painter.measureLabelWidth(label.getLabel(), chartConfiguration.getLabelSize()) / 2);
            label.setScreenPositionY(labelsBottomPosition - heightBetweenLabels * i);
            list.add(label);
            i++;
        }

        yLabels.clear();
        yLabels.addAll(list);
    }

    private void placeDataPoints(Frame innerFrame) {

        float scaleSize = chartConfiguration.getScale().getSize();
        float chartHeight = innerFrame.getBottom() - innerFrame.getTop();
        float halfBarWidth = (innerFrame.getRight() - innerFrame.getLeft()) / xLabels.size() / 2;
        float labelsLeftPosition = innerFrame.getLeft() + halfBarWidth;
        float labelsRightPosition = innerFrame.getRight() - halfBarWidth;
        float widthBetweenLabels = (labelsRightPosition - labelsLeftPosition) / (xLabels.size() - 1);

        List<DataPoint> list = new ArrayList<>();
        int index = 0;
        for (DataPoint dataPoint : data){
            dataPoint.setScreenPositionX(labelsLeftPosition + (widthBetweenLabels * index));
            dataPoint.setScreenPositionY(innerFrame.getBottom() -
                    // bar length must be positive, or zero
                    (chartHeight * Math.max(
                            0f,
                            dataPoint.getValue() - chartConfiguration.getScale().getMin()
                    ) / scaleSize));
            list.add(dataPoint);
            index++;
        }
        data.clear();
        data.addAll(list);

    }
}
